﻿////////////////////////////////////////////////////////////////////////////////////////////////
//
// Copyright © Yaroslavov Alexander 2010
//
// Contacts:
// Phone: +7(906)827-27-51
// Email: x-ronos@yandex.ru
//
/////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Moppet.Lapa
{
	// Внимание, нельзя в этом классе использовать неявное приведение к типу LpsParser!
	// Это приводит к тому, что приведение проявляет себя совсем не там, где следует.
	//



	/// <summary>
	/// Cписок альтернатив. Этот класс упрощает комбинацию парсеров. Например, возьмём комбинацию парсеров
	/// p1 | p2 | p3 | p4, если все 4-е парсера относятся к типу LpsParser, тогда будет
	/// сформирован список альтернатив, который далее будет скомбинирован в один оптимальный парсер и при этом будет
	/// выбираться первая успешная альтернатива.
	/// </summary>
	public sealed class LpsAlternatives : LpParserAttrs<LpsAlternatives>
	{
		/// <summary>
		/// Список парсеров.
		/// </summary>
		List<LpsParser> m_parsers = new List<LpsParser>();

		/// <summary>
		/// Конструктор по умолчанию.
		/// </summary>
		public LpsAlternatives()
		{
		}

		/// <summary>
		/// Вспомогательный конструктор.
		/// </summary>
		/// <param name="left">Левая цепочка.</param>
		/// <param name="right">Правая цепочка.</param>
		public LpsAlternatives(IEnumerable<LpsParser> left, IEnumerable<LpsParser> right)
		{
            if (left.Any(l => l == null))
                throw new ArgumentNullException("left", "One of item in arguments is null.");
            if (right.Any(r => r == null))
                throw new ArgumentNullException("right", "One of item in arguments is null.");

            m_parsers = new List<LpsParser>(left.Count() + right.Count());
            m_parsers.AddRange(left);
            m_parsers.AddRange(right);
		}

		/// <summary>
		/// Вспомогательный конструктор.
		/// </summary>
		/// <param name="parsers">Парсеры.</param>
		public LpsAlternatives(params LpsParser[] parsers)
		{
			m_parsers = new List<LpsParser>(parsers.Length);
			foreach (var p in parsers)
			{
				if (p == null)
					throw new ArgumentNullException("parsers", "One of item in arguments is null.");
				m_parsers.Add(p);
			}
		}

		/// <summary>
		/// Копия объекта.
		/// </summary>
		/// <returns>Клон.</returns>
		public override LpsAlternatives Copy()
		{
			var c = base.Copy();
			c.m_parsers = new List<LpsParser>(m_parsers.Count + 1);
			c.m_parsers.AddRange(m_parsers);
			return c;
		}


		/// <summary>
		/// Вспомогательная функция конвертации цепочки в парсер выбора первой успешной альтернативы.
		/// От порядка расположения парсеров (альтернатив) зависит скорость.
		/// </summary>
		/// <returns>Парсер.</returns>
		public LpsParser TakeFirst()
		{
			return TakeFirst(Identifier, m_parsers);
		}

		/// <summary>
		/// Комбинатор, который выбирает ту первую успешную альтернативу (результат парсера), за которой выполняется постусловие behind.
		/// Использование данной функции особенно выгодно, если альтернативы отсортированы в порядке уменьшения вероятности
		/// их появления.
		/// </summary>
		/// <param name="ifBehind">Постусловие.</param>
		/// <returns>Результирующий парсер.</returns>
		public LpsParser TakeFirst(LpsParser ifBehind)
		{
			return TakeFirst(Identifier, m_parsers, ifBehind);
		}

		/// <summary>
		/// Вспомогательная функция конвертации цепочки в парсер, который выберет наиболее длинное соответствие - жадный алгоритм.
		/// </summary>
		/// <returns>Парсер.</returns>
		public LpsParser TakeMax()
		{
			var copyParsers = m_parsers.ToArray();
            return new LpsParser(Identifier, (text) => All(text, copyParsers).Max() ?? new LpNode(Identifier, text));
		}

		/// <summary>
		/// Вспомогательная функция конвертации цепочки в парсер выбора всех альтернатив.
		/// </summary>
		/// <returns>Парсер.</returns>
		public LpmParser TakeAll()
		{
			var copyParsers = m_parsers.ToArray();
			return new LpmParser(Identifier, (text) => All(text, copyParsers).DistinctMatches()); // DistinctVoids
		}


		/// <summary>
		/// Оператор неявного приведения типа в мультипарсер.
		/// </summary>
		/// <param name="parsers">список альтернатив.</param>
		/// <returns>Парсер.</returns>
		public static implicit operator Func<LpText, IEnumerable<LpNode>>(LpsAlternatives parsers)
		{
			return parsers.TakeAll().Parser;
		}
		

		/// <summary>
		/// Оператор неявного приведения типа.
		/// </summary>
		/// <param name="parsers">список альтернатив.</param>
		/// <returns>Парсер.</returns>
		public static implicit operator Func<LpText, LpNode>(LpsAlternatives parsers)
		{
			return parsers.TakeFirst().Parser;
		}

		/// <summary>
		/// Оператор конкатенации парсера к цепочке альтернатив.
		/// </summary>
		/// <param name="left">Цепочка парсеров.</param>
		/// <param name="right">Правый однорезультатный парсер.</param>
		/// <returns>Цепочка парсеров.</returns>
		public static LpsChain operator + (LpsAlternatives left, LpsParser right)
		{
			if (right == null)
				throw new ArgumentNullException("right");
			return left.TakeFirst() + right;
		}


		/// <summary>
		/// Оператор последовательной комбинации парсеров.
		/// </summary>
		/// <param name="left">Парсер.</param>
		/// <param name="right">Список альтернатив.</param>
		/// <returns>Список альтернатив.</returns>
		public static LpsChain operator + (LpsParser left, LpsAlternatives right)
		{
			if (left == null)
				throw new ArgumentNullException("left");
			return left + right.TakeFirst();
		}


		/// <summary>
		/// Оператор последовательной комбинации парсеров.
		/// </summary>
		/// <param name="left">список альтернатив.</param>
		/// <param name="right">список альтернатив.</param>
		/// <returns>Парсер.</returns>
		public static LpsChain operator + (LpsAlternatives left, LpsAlternatives right)
		{
			return left.TakeFirst() + right.TakeFirst();
		}

		/// <summary>
		/// Оператор последовательной комбинации парсеров.
		/// </summary>
		/// <param name="left">список альтернатив.</param>
		/// <param name="right">Поиск одного (следующего) символа.</param>
		/// <returns>Парсер.</returns>
		public static LpsChain operator + (LpsAlternatives left, char right)
		{
			return left.TakeFirst() + Lp.Char(right);
		}

		/// <summary>
		/// Оператор последовательной комбинации парсеров.
		/// </summary>
		/// <param name="left">список альтернатив.</param>
		/// <param name="right">Поиск одного (следующего) слова.</param>
		/// <returns>Парсер.</returns>
		public static LpsChain operator + (LpsAlternatives left, string right)
		{
			if (right == null)
				throw new ArgumentNullException("right");
			return left.TakeFirst() + Lp.Term(right);
		}

		/// <summary>
		/// Оператор конкатенации мултипарсера к цепочке альтернатив.
		/// </summary>
		/// <param name="left">Список альтернатив.</param>
		/// <param name="right">Правый мультипарсер.</param>
		/// <returns>Мультипарсер.</returns>
		public static LpmParser operator + (LpsAlternatives left, LpmParser right)
		{
			if (right == null)
				throw new ArgumentNullException("right");
			return left.TakeFirst() + right;
		}

		/// <summary>
		/// Оператор конкатенации цепочки альтернатив к мултипарсеру.
		/// </summary>
		/// <param name="left">Правый мультипарсер.</param>
		/// <param name="right">Список альтернатив.</param>
		/// <returns>Мультипарсер.</returns>
		public static LpmParser operator + (LpmParser left, LpsAlternatives right)
		{
			if (left == null)
				throw new ArgumentNullException("left");
			return left + right.TakeFirst();
		}

		/// <summary>
		/// Оператор Or. Либо цепочка либо мультипарсер.
		/// </summary>
		/// <param name="left">список альтернатив.</param>
		/// <param name="right">Правый мультипарсер.</param>
		/// <returns>Мультипарсер.</returns>
		public static LpmParser operator | (LpsAlternatives left, LpmParser right)
		{
			if (right == null)
				throw new ArgumentNullException("right");
			return left.TakeFirst() | right;
		}

		/// <summary>
		/// Оператор конкатенации цепочек альтернатив.
		/// </summary>
		/// <param name="left">Список альтернатив.</param>
		/// <param name="right">Список альтернатив.</param>
		/// <returns>Результирующая список альтернатив.</returns>
		public static LpsAlternatives operator | (LpsAlternatives left, LpsAlternatives right)
		{
			return new LpsAlternatives(left.m_parsers, right.m_parsers);
		}

		/// <summary>
		/// Оператор конкатенации списка альтернатив с цепочкой парсеров.
		/// </summary>
		/// <param name="left">Список альтернатив.</param>
		/// <param name="right">Цепочка парсеров.</param>
		/// <returns>Результирующий список альтернатив.</returns>
		public static LpsAlternatives operator | (LpsAlternatives left, LpsChain right)
		{
			return left.TakeFirst() | right.ToParser();
		}

		/// <summary>
		/// Оператор конкатенации парсера к списку альтернатив.
		/// </summary>
		/// <param name="left">Список альтернатив.</param>
		/// <param name="ch">Парсер одного символа.</param>
		/// <returns>Список альтернатив.</returns>
		public static LpsAlternatives operator | (LpsAlternatives left, char ch)
		{
			return new LpsAlternatives(left.m_parsers, new[] { Lp.Char(ch) });
		}

		/// <summary>
		/// Оператор конкатенации списка альтернатив к парсеру символа.
		/// </summary>
		/// <param name="ch">Парсер одного символа.</param>
		/// <param name="right">Список альтернатив.</param>
		/// <returns>Список альтернатив.</returns>
		public static LpsAlternatives operator | (char ch, LpsAlternatives right)
		{
			return new LpsAlternatives(new[] { Lp.Char(ch) }, right.m_parsers);
		}

		/// <summary>
		/// Оператор конкатенации парсера к списку альтернатив.
		/// </summary>
		/// <param name="left">Список альтернатив.</param>
		/// <param name="term">Парсер одного слова - терма.</param>
		/// <returns>Список альтернатив.</returns>
		public static LpsAlternatives operator | (LpsAlternatives left, string term)
		{
			if (term == null)
				throw new ArgumentNullException("term");
			return new LpsAlternatives(left.m_parsers, new[] { Lp.Term(term) });
		}

		/// <summary>
		/// Оператор конкатенации парсера к списку альтернатив.
		/// </summary>
		/// <param name="left">Список альтернатив.</param>
		/// <param name="right">Парсер.</param>
		/// <returns>Список альтернатив.</returns>
		public static LpsAlternatives operator | (LpsAlternatives left, LpsParser right)
		{
			if (right == null)
				throw new ArgumentNullException("right");
			return new LpsAlternatives(left.m_parsers, new [] { right });
		}

		/// <summary>
		/// Оператор конкатенации списка альтернатив к парсеру.
		/// </summary>
		/// <param name="left">Парсер.</param>
		/// <param name="right">Список альтернатив.</param>
		/// <returns>Список альтернатив.</returns>
		public static LpsAlternatives operator | (LpsParser left, LpsAlternatives right)
		{
			return new LpsAlternatives(new[] { left }, right.m_parsers);
		}

		/// <summary>
		/// Оператор конкатенации цепочки парсеров со списком альтернатив.
		/// </summary>
		/// <param name="left">Цепочка парсеров.</param>
		/// <param name="right">Список альтернатив.</param>
		/// <returns>Результирующий список альтернатив.</returns>
		public static LpsAlternatives operator | (LpsChain left, LpsAlternatives right)
		{
			return left.ToParser() | right.TakeFirst();
		}

		/// <summary>
		/// Оператор Or. Либо мультипарсер либо цепочка.
		/// </summary>
		/// <param name="left">Левый мультипарсер.</param>
		/// <param name="right">Цепочка парсеров.</param>
		/// <returns>Мультипарсер.</returns>
		public static LpmParser operator | (LpmParser left, LpsAlternatives right)
		{
			return left | right.TakeFirst();
		}


		/// <summary>
		/// Комбинатор для выбора первой успешной альтернативы.
		/// </summary>
		/// <param name="parsers">Список парсеров.</param>
		/// <returns>Результирующий парсер.</returns>
		public static LpsParser TakeFirst(IEnumerable<LpsParser> parsers)
		{
			return TakeFirst(null, parsers);
		}

		/// <summary>
		/// Комбинатор для выбора первой успешной альтернативы.
		/// </summary>
		/// <param name="id">Идентификатор результата.</param>
		/// <param name="parsers">Список парсеров.</param>
		/// <returns>Результирующий парсер.</returns>
		public static LpsParser TakeFirst(string id, IEnumerable<LpsParser> parsers)
		{
			var parsersArray = parsers.ToArray();
			return new LpsParser(id, (text) =>
			{
				foreach (var next in parsersArray)
				{
					var res = next.Do(text);
                    if (res.Match.Length >= 0)
						return res;
				}
                return new LpNode(text);
			});
		}

		/// <summary>
		/// Комбинатор, который выбирает ту первую успешную альтернативу, за которой выполняется постусловие behind.
		/// Использование данной функции особенно выгодно, если альтернативы отсортированы в порядке уменьшения вероятности
		/// их появления.
		/// </summary>
		/// <param name="id">Идентификатор парсера.</param>
		/// <param name="parsers">Список парсеров, который в своей совокупности представляют варианты разбора.</param>
		/// <param name="behind">Постусловие.</param>
		/// <returns>Результирующий парсер.</returns>
		public static LpsParser TakeFirst(string id, IEnumerable<LpsParser> parsers, LpsParser behind)
		{
			var parsersArray = parsers.ToArray();
			return new LpsParser(id, (text) =>
			{
				foreach (var parser in parsersArray)
				{
					var res = parser.Do(text);
                    if (res.Match.Length >= 0)
					{
						if (behind.Do(res.Rest).Success)
							return res;
					}
				}
                return new LpNode(text);
			});
		}

		/// <summary>
		/// Комбинатор для выбора всех альтернатив.
		/// </summary>
		/// <param name="text">Блок текста.</param>
		/// <param name="parsers">Список парсеров.</param>
		/// <returns>Результирующий парсер.</returns>
		private static IEnumerable<LpNode> All(LpText text, IEnumerable<LpsParser> parsers)
		{
			foreach (var next in parsers)
			{
				var res = next.Do(text);
				if (res.Match.Length >= 0)
					yield return res;
			}
		}
	}
}
